/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once

#include <Windows.h>

#include <D3D11.h>

#include "Hogshead.Common.h"

#include "../EnumConstDeclarations/Shaders.h"
#include "../HLSLCPPDeclarations/Lighting.fx"


struct ID3D11Buffer;
struct ID3D10Blob;

struct ID3D11ShaderResourceView;
struct ID3D11DepthStencilView;
struct ID3D11RenderTargetView;

struct ID3D11Texture1D;
struct ID3D11Texture2D;
struct ID3D11InputLayout;

struct ID3D11RasterizerState;
struct ID3D11BlendState;
struct ID3D11SamplerState;
struct ID3D11ClassLinkage;

struct ID3D11PixelShader;
struct ID3D11VertexShader;
struct ID3D11GeometryShader;

struct IDXGISwapChain;
struct ID3D11Device;
struct ID3D11DeviceContext;
struct ID3D11Resource;

enum D3D_FEATURE_LEVEL;
enum D3D11_DEPTH_WRITE_MASK;
enum D3D11_COMPARISON_FUNC;
enum D3D_PRIMITIVE_TOPOLOGY;

struct D3D11_SAMPLER_DESC;
struct D3D11_INPUT_ELEMENT_DESC;
struct D3D11_BUFFER_DESC;
struct D3D11_RASTERIZER_DESC;
struct D3D11_SUBRESOURCE_DATA;
struct D3D11_TEXTURE1D_DESC;
struct D3D11_TEXTURE2D_DESC;
struct D3D11_BLEND_DESC;
struct D3D11_SO_DECLARATION_ENTRY;
struct D3D11_RENDER_TARGET_VIEW_DESC;
struct D3D11_SHADER_RESOURCE_VIEW_DESC;
struct D3D11_DEPTH_STENCIL_DESC;

class hkMemoryRouter;

namespace Hogshead
{
	namespace DX11Rendering
	{
		class Texture;

		struct BasicDynamic;
		struct DiffuseDynamic;
		struct SpecularDynamic;
		struct GameInfo;
		struct BasicStatic;

		enum ConstantBufferType;
		enum ShaderType;

		class DX11Manager
		{
		public:

#pragma region Screen State

			/**
			* Whether the screen is full or not
			**/
			enum FullScreenState
			{
				WINDOWED = FALSE, 
				FULLSCREEN = TRUE 
			};

			/**
			* The function for handling all windows messages
			*
			* @see http://forums.xna.com/forums/t/57010.aspx
			*
			* @param in_window_handle The window handle for the window
			* @param in_message The window message to handle
			* @result The success of the message handling
			**/
			static LRESULT CALLBACK DX11Manager::handleWindowsMessage(HWND in_window_handle, UINT in_message, WPARAM in_word, LPARAM in_long_word);

			static void preDraw();

			static void setDesiredResolution(int in_width, int in_height);
			static void setDesiredScreenState(FullScreenState in_state);

			enum ScreenChangeFlags : int
			{
				NONE = 0,
				CHANGE_RESOLUTION_SETTING = 1,
				CHANGE_FULLSCREEN_SETTING = 2
			};

			static UINT getWidth();
			static UINT getHeight();			
			
		private: 
			static void resizeScreen();
			
			static int _screen_change_flags;

			/**
			* Set the full screen state of the window
			* 
			* @param in_desired_state The state to change the window to
			**/
			static void setFullScreenState(FullScreenState in_state);

			static int _desired_screen_resolution_x;
			static int _desired_screen_resolution_y;
			static FullScreenState _desired_fullscreen_state;

			static bool _finalized;
			
			static FullScreenState _fullscreen_state;
			static HWND _window_handle;

			static DWORD _width;
			static DWORD _height;

			static int max_width;
			static int max_height;

			//DX11 objects
			static IDXGISwapChain* _swap_chain;
			static ID3D11Device* _d3d11_device;
			static ID3D11DeviceContext* _d3d11_device_context;		

			static D3D_FEATURE_LEVEL* _highest_feature_level_capable;

#pragma endregion

			
#pragma region Render Target, Shader Resource, and Depth Stencil Control

		private:
			/* the back buffer */
			static ID3D11RenderTargetView* _back_buffer_render_target_view;			

			/* the depth/stencil buffer associatted with the back buffer */
			static ID3D11DepthStencilView* _depth_buffer_depth_stencil_view;

			/* an array used to send RTV's to the GPU */
			static ID3D11RenderTargetView* _render_targets[8];

		public:

			static void setViewport( D3D11_VIEWPORT* in_viewport );

			static void clearBackBuffer(const Color& in_clear_color = Color::black);

			static void clearDepthBuffer(UINT in_flags, float in_depth, UINT8 in_stencil);

			/**
			* Called to clear a specific render target to a given color
			*
			* @param in_render_target_view The RenderTarget to clear
			* @param in_clear_color The color to fill the buffer with, default is black
			*/
			static void clearRenderTarget(ID3D11RenderTargetView* in_render_target_view, const Color& in_clear_color = Color::black);

			/**
			* Called to set upto 7 render targets, also binds the back buffer to SV_TARGET0 and the associatted DepthStencil Buffer.
			*
			* @param in_render_target_views a pointer to an array of ID3D11RenderTargetView*'s to bind starting from SV_TARGET1
			* @param in_number the number of ID3D11RenderTargetView's in the array in_render_target_views.
			**/
			static void setRenderTargetsStandard(const Vector<ID3D11RenderTargetView*>* in_render_target_views, int in_number );

			/**
			* Called to set upto 8 render targets and a depth stencil 
			*
			* @param in_render_target_views a pointer to an array of ID3D11RenderTargetView*'s to bind starting from SV_TARGET0
			* @param in_number the number of ID3D11RenderTargetView's in the array in_render_target_views.
			* @param in_depth_stencil_view The ID3D11DepthStencilView to set.
			**/
			static void setRenderTargetsCustom(const Vector<ID3D11RenderTargetView*>* in_render_target_views, int in_number, ID3D11DepthStencilView* in_depth_stencil_view );


			/**
			* Called to bind a depth stencil that is going to be written to
			*
			* @param in_shadow_map_buffer The DepthStencil we will write depth values from light to
			*/
			static void setShadowMapWrite( ID3D11DepthStencilView* in_shadow_map_buffer );

			/**
			* Create a shader resource view from a shader resource.
			*
			* @param in_resource The shader resource
			* @param in_desc The shader resource description.
			* @param out_view The shader resource view created.
			* @return The result of the shader resource view creation.
			**/
			static HRESULT createShaderResourceView(ID3D11Resource* in_resource, const D3D11_SHADER_RESOURCE_VIEW_DESC* in_desc, ID3D11ShaderResourceView** out_view);

			/**
			* Create an ID3D11Texture2D
			*
			* @param in_desc The texture description.
			* @param in_init_data The initial data to populate the array.
			* @param out_texture_2d The texture created.
			* @return The result of the texture creation.
			**/
			static HRESULT createTexture2D(const D3D11_TEXTURE2D_DESC *in_desc, const D3D11_SUBRESOURCE_DATA *in_initial_data, ID3D11Texture2D **in_texture_2d, const char* in_opt_name = NULL, int in_opt_length = 0);

			static HRESULT createDepthStencilView(ID3D11Resource* in_texture, const D3D11_DEPTH_STENCIL_VIEW_DESC* in_description, ID3D11DepthStencilView** out_view);

			/**
			* Create a render target view from a render target.
			* 
			* @param in_resource The resource to create the render target view for.
			* @param in_desc The shader resource view description.
			* @param in_rt_view The render target view created.
			* @result The result of the render target view creation.
			**/
			static HRESULT createRenderTargetView(ID3D11Resource *in_resource, const D3D11_RENDER_TARGET_VIEW_DESC *in_desc, ID3D11RenderTargetView **in_rt_view);
			/**
			* Load a texture from a file
			* 
			* @param The Texture object that will store the shader resource
			**/
			static HRESULT loadTexture(Texture* out_texture_resource);

			/**
			* Create an ID3D11Texture1D
			*
			* @param in_desc The texture description.
			* @param in_init_data The initial data to populate the array.
			* @param out_texture_1d The texture created.
			* @return The result of the texture creation.
			**/
			static HRESULT createTexture1D(const D3D11_TEXTURE1D_DESC *in_desc, const D3D11_SUBRESOURCE_DATA *in_init_data, ID3D11Texture1D** out_texture_1d);

			
			
			static void setDepthStencilState(bool in_depth_enable, D3D11_DEPTH_WRITE_MASK in_depth_write_mask, D3D11_COMPARISON_FUNC in_comp_func);
			static void setDepthStencilState(ID3D11DepthStencilState* in_depth_stencil_state);
			static void clearDepthStencil(ID3D11DepthStencilView* in_render_depth_stencil, UINT in_flags, float in_depth, UINT8 in_stencil);

#pragma endregion


#pragma region Utility Functions

			
			/**
			* This intializes the window for DirectX 11 use
			*
			* @param _window_handle The window handle for the window to use
			* @return An HRESULT signifying whether the window was intialized or not.
			**/
			static HRESULT DX11Manager::initialize(HWND window_handle);

			/**
			* This method should called to clean up all DirectX 11 logic
			**/
			static void DX11Manager::finalize();
		
			

#pragma endregion

#pragma region Lighting

		private:
			/**
			* Stores lightinging information
			**/
			static ID3D11Buffer* _lighting_buffer;

		public:
			

			static ID3D11Buffer*& getLightingConstantBuffer();

#pragma endregion

#pragma region Shaders
		public:
			/**
			* Create a vertex shader from a blob
			* To create a blob use {@link #Shader::CompileShaderFromFile(LPCSTR in_file_name, 
			*									LPCSTR in_shader_entry_point,
			*									LPCSTR in_shader_model,
			*									ID3DBlob** out_blob)}
			*
			* @param in_blob The blob to create the vertex shader from
			* @param out_vs The vertex shader created
			* @return The result of the vertex shader creation
			**/
			static HRESULT createVSFromBlob(ID3D10Blob* in_blob, ID3D11VertexShader** out_vs);

			/**
			* Create a geometry shader from a blob
			* Note: The geometry shader will be created without a stream output
			* To create a blob use {@link #Shader::CompileShaderFromFile(LPCSTR in_file_name, 
			*									LPCSTR in_shader_entry_point,
			*									LPCSTR in_shader_model,
			*									ID3DBlob** out_blob)}
			*
			* @param in_blob The blob
			* @param out_gs The geometry shader created
			* @return The result of the geometry shader creation
			**/
			static HRESULT createGSFromBlob(ID3D10Blob* in_blob, ID3D11GeometryShader** out_gs);

			/**
			* Create a geometry shader with a stream output from a blob
			* Stream outputs are used to write to a vertex buffer on the CPU rather than the rendering pipeline.
			* Note: The geometry shader will be created with a stream output
			*
			* To create a blob use {@link #Shader::CompileShaderFromFile(LPCSTR in_file_name, 
			*									LPCSTR in_shader_entry_point,
			*									LPCSTR in_shader_model,
			*									ID3DBlob** out_blob)}
			*
			* @param in_shader_bytecode A pointer to the compiled shader. 
			* @param in_bytecode_length Size of the compiled geometry shader.
			* @param in_SO_declaration Pointer to a D3D11_SO_DECLARATION_ENTRY array. Cannot be NULL if NumEntries > 0.
			* @param in_num_entries The number of entries in the stream output declaration ( ranges from 0 to {@link #D3D11_SO_STREAM_COUNT} * {@link #D3D11_SO_OUTPUT_COMPONENT_COUNT} ).
			* @param in_buffer_strides An array of stride lengths in the stream output buffer
			* @param in_num_strides The number of strides (or buffers) in pBufferStrides (ranges from 0 to D3D11_SO_BUFFER_SLOT_COUNT).
			* @param in_rasterized_stream The index number of the stream to be sent to the rasterizer stage (ranges from 0 to D3D11_SO_STREAM_COUNT - 1). Set to D3D11_SO_NO_RASTERIZED_STREAM if no stream is to be rasterized.
			* @param in_class_linkage A pointer to a class linkage interface (see ID3D11ClassLinkage); the value can be NULL.
			* @param out_geometry_shader Address of a pointer to an ID3D11GeometryShader  interface, representing the geometry shader that was created. Set this to NULL to validate the other parameters; if validation passes, the method will return S_FALSE instead of S_OK.
			* @return The result of the geometry shader creation.
			**/
			static HRESULT createGeometryShaderWithStreamOutput( const void *in_shader_bytecode,
													SIZE_T in_bytecode_length,
													const D3D11_SO_DECLARATION_ENTRY *in_SO_declaration,
													UINT in_num_entries,
													const UINT *in_buffer_strides,
													UINT in_num_strides,
													UINT in_rasterized_stream,
													ID3D11ClassLinkage *in_class_linkage,
													ID3D11GeometryShader **out_geometry_shader); 

			/**
			* Create a pixel shader from a blob
			*
			* To create a blob use {@link #Shader::CompileShaderFromFile(LPCSTR in_file_name, 
			*									LPCSTR in_shader_entry_point,
			*									LPCSTR in_shader_model,
			*									ID3DBlob** out_blob)}
			*
			* @param in_blob The blob containing the pixel shader code.
			* @param out_ps The pixel shader created.
			* @return The result of the pixel shader creation.
			**/
			static HRESULT createPSFromBlob(ID3D10Blob* in_blob, ID3D11PixelShader** out_ps);


			/**
			* Create a Hull Shader from a blob
			*/
			static HRESULT createHSFromBlob( ID3DBlob* in_blob, ID3D11HullShader** out_hs);


			/**
			* Create a Domain Shader from a blob
			*/
			static HRESULT createDSFromBlob( ID3DBlob* in_blob, ID3D11DomainShader** out_ds);

			/**
			* Set the shaders on the graphics card
			*
			* @param in_vertex_shader The vertex shader to set.
			* @param in_geometry_shader The geometry shader to set.
			* @param in_pixel_shader The pixel shader to set.
			**/
			static void setShaders(ID3D11VertexShader* in_vertex_shader, ID3D11GeometryShader* in_geometry_shader, ID3D11PixelShader* in_pixel_shader);

			/**
			* Set the vertex shader on the graphics card.
			*
			* @param in_vertex_shader The vertex shader to set.
			**/
			static void setVertexShader(ID3D11VertexShader* in_vertex_shader);


			/**
			* Set the Hull Shader on the Graphics Card.
			*/
			static void setHullShader(ID3D11HullShader* in_hull_shader);

			/**
			* Set the Domain Shader on the Graphics Card.
			*/
			static void setDomainShader(ID3D11DomainShader* in_domain_shader);

			/**
			* Set the geometry shader on the card.
			*
			* @param in_geometry_shader The geometry shader to set.
			**/
			static void setGeomentryShader(ID3D11GeometryShader* in_geometry_shader);

			/**
			* Set the Pixel shader on the card.
			*
			* @param in_pixel_shader The pixel shader to set.
			**/
			static void setPixelShader(ID3D11PixelShader* in_pixel_shader);

			/**
			* Set an array of shader resource views for use by the pixel shader.
			*
			* @param in_slot The beginning slot to set the shader resource views.
			* @param in__resource_view The array of resource views to set.
			**/
			static void setPSShaderResource(UINT in_slot, ID3D11ShaderResourceView* const* in_resource_view);

			/**
			*
			* @param in_shader_resources a pointer to an array of ID3D11ShaderResourceView pointers.
			* @param in_number, the number of shader resources in the array.
			**/
			static void setPSShaderResourcesCustom(ID3D11ShaderResourceView* const* in_shader_resources, UINT in_number, UINT in_start_register);

			/**
			* Set an array of shader resource views for use by the geometry shader.
			*
			* @param in_slot The beginning slot to set the shader resource views.
			* @param in__resource_view The array of resource views to set.
			**/
			static void setGSShaderResource(UINT in_slot, ID3D11ShaderResourceView* const* in_resource_view);
			
			/**
			* Set an array of shader resource views for use by the vertex shader.
			*
			* @param in_slot The beginning slot to set the shader resource views.
			* @param in__resource_view The array of resource views to set.
			**/
			static void setVSShaderResource(UINT in_slot, ID3D11ShaderResourceView* const* in_resource_view);

			static void setShaderResources(UINT in_slot, ID3D11ShaderResourceView* const* in_resource_view, ShaderBindFlags in_flags);
			

			static void setHSShaderResource(UINT in_slot, ID3D11ShaderResourceView* const* in_resource_view);

			static void setDSShaderResource(UINT in_slot, ID3D11ShaderResourceView* const * in_resource_view);

			/**
			* Create an input layout
			*
			* @param in_layout_desc The input layout description.
			* @param in_number_of_elements.
			* @param in_vs_blob The vertex shader blob used to create the input layout.
			* @return The input layout created.
			**/
			static ID3D11InputLayout* createLayout(const D3D11_INPUT_ELEMENT_DESC* in_layout_desc, UINT in_number_of_elements, ID3D10Blob* in_vs_blob);
#pragma endregion
			
#pragma region Buffer Creation
			//buffer creation
			static HRESULT createBuffer(D3D11_BUFFER_DESC* in_buffer_description, D3D11_SUBRESOURCE_DATA* in_subresource_data, ID3D11Buffer** out_buffer);
#pragma endregion
			

#pragma region Process Input and Output

				
			/**
			* Create a constant buffer.
			*
			* @param in_byte_size The byte size of the constant buffer to create.
			* @param out_constant_buffer_interface The constant buffer created.
			* @return The result of the constant buffer creation.
			**/
			static HRESULT createConstantBuffer(UINT in_byte_size, ID3D11Buffer** out_constant_buffer_interface);

			/**
			* Bind an array of constant buffers for use by a shader.
			*
			* @param in_constant_buffer The array of constant buffers to bind.
			* @param in_register The first register to set the constant buffers to.
			* @param in_shader_binds The flags that signify which shader types will use the constant buffer
			**/
			static void bindConstantBuffer(ID3D11Buffer*const* in_constant_buffer, UINT in_register, ShaderBindFlags in_shader_binds);

			/**
			* Update a constance buffer
			* 
			* @param in_destination The buffer to copy the data to.
			* @param in_source The data to copy to the buffer.
			**/
			static void updateConstantBuffer(ID3D11Buffer* in_destination, const void* in_source);

			/**
			* Update the current vertex buffer used for drawing
			*
			* @param in_vertex_buffer The vertex buffer to use.
			* @param in_vertex_size The number of vertices in the vertex buffer.
			**/
			static void setVertexBuffers(UINT in_start_slot, UINT in_num_buffers, ID3D11Buffer* const* in_vertex_buffer, UINT in_vertex_size, UINT in_offset);

			/**
			* Update the current vertex buffer used for drawing
			*
			* @param in_vertex_buffer The vertex buffer to use.
			* @param in_vertex_size The number of vertices in the vertex buffer.
			**/
			static void setVertexBuffer(ID3D11Buffer* in_vertex_buffer, UINT in_vertex_size);

			/**
			* Set the stream output targets to be used in the draw call
			*
			* @param in_num_buffers The number of stream output targets to set.
			* @ param in_so_targets The array of stream output targets to set.
			**/
			static void setSOTargets(UINT in_num_buffers, ID3D11Buffer* const* in_so_targets);

			/**
			* Create a rasterizer state.
			*
			* @param in_desc The rasterizer state description.
			* @param in_rasterizer_state The rasterize state object that will be created.
			**/
			static HRESULT createRasterizerState(const D3D11_RASTERIZER_DESC* in_desc, ID3D11RasterizerState** in_rasterizer_state);

			/**
			* Create a sampler state
			*
			* @param in_desc The sampler state description.
			* @param in_rasterizer_state The rasterizer state that is created.
			**/
			static HRESULT createSamplerState(const D3D11_SAMPLER_DESC* in_desc, ID3D11SamplerState** in_rasterizer_state);

			/**
			* Create a blend state
			*
			* @param in_desc The blend state description.
			* @param in_blend_state The blend state that will be created.
			**/
			static HRESULT createBlendState(const D3D11_BLEND_DESC* in_desc, ID3D11BlendState** in_blend_state);

			/**
			* Create a Depth Stencil state
			*
			* @param in_desc The depth stencil state description.
			* @param in_blend_state The depth stencil state that will be created.
			**/
			static HRESULT createDepthStencilState(const D3D11_DEPTH_STENCIL_DESC* in_desc, ID3D11DepthStencilState** in_depth_stencil_state);

			/**
			* Set a Rasterizer state 
			*
			* @param in_rasterizer_state The raster state to set.
			**/
			static void setRasterizerState(ID3D11RasterizerState* in_rasterizer_state);

			/**
			* Set a blend state to be used by the draw call
			*
			* @param in_blend_state The blend state to set.
			* @param in_blend_factor An array of blend factors, one for each RGBA component.
			* @param in_sample_mask A 32 bit sample mask. The default value 0xffffffff.
			**/
			static void setBlendState(ID3D11BlendState* in_blend_state, const FLOAT in_blend_factor[4], UINT in_sample_mask);

			/**
			* Set an array of samplers to be used by a shader.
			*
			* @param in_start_slot The register where the first sampler will go. Subsequent samplers will be placed in incremental registers.
			* @param in_num_samplers The number of samplers to set on the shader.
			* @param in_shader_type The type of shader that will use these samplers, e.g. Vertex, Pixel, Geometry
			**/
			static void setSamplers(UINT in_start_slot, UINT in_num_samplers, ID3D11SamplerState* const* in_samplers, ShaderBindFlags in_shader_type);
#pragma endregion

#pragma region Draw Functions

		
			
			static void setTopology(D3D_PRIMITIVE_TOPOLOGY in_topo);

			static void setInputLayout(ID3D11InputLayout* in_layout);


			//draw
			static void draw(int in_num_primitive_topos, int in_start_vertex);
			static void drawAuto();

			static void present();
#pragma endregion
			
			static void updateSubresource(ID3D11Resource* in_resource, const void* in_data, UINT in_source_row);

			static Logger* _logger;
	


			

		



#ifdef HOGSHEADED

			class WindowBundle
			{
			//used to hold onto the data for each window
			public:
				WindowBundle()
				{
					_width = (DWORD)0;
					_height = (DWORD)0;

					_swap_chain = NULL;
				};

				~WindowBundle()
				{
					if(_swap_chain)
						_swap_chain->Release();
					
				};

				WindowBundle(const WindowBundle& in_wb)
				{
					_width = in_wb._width;
					_height = in_wb._height;

					_swap_chain = in_wb._swap_chain;
				};

				WindowBundle& operator=(const WindowBundle& in_wb)
				{
					_width = in_wb._width;
					_height = in_wb._height;

					_swap_chain = in_wb._swap_chain;
					return *this;
				}

				void finalize()
				{
					if(_swap_chain)
					{
						_swap_chain->Release();
						_swap_chain = NULL;
					}
				}

				DWORD _width;
				DWORD _height;

				IDXGISwapChain* _swap_chain;
				ID3D11RenderTargetView* _render_target;
			};

			static IDXGIFactory* _dxgi_factory;

			static Vector<WindowBundle> _editor_windows;

			//used to add a new viewport to render to, return the int handle to it
			static int initializeEditorViewport(HWND in_window_handle);

			static void editorCleanup();
#endif
			
		};
	}
}
